from pyreadstat import pyreadstat
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pandas.api.types import CategoricalDtype
from scipy.stats import norm


def one_prop_test(n, n1, pi0):
    """
    函数功能：一个总体比例的检验
    n:   样本规模，如：共抽取了2000人。
    n1:  某个特征的规模，如：抽取的2000人中，有450人收看了某节目。n1=450
    pi0: 需要检验的总体比例。 
    参考文献：贾俊平. (2021). 统计学——Python实现_. 高等教育出版社.
    """
    n = n
    p = n1 / n
    pi0 = pi0
    z = (p - pi0) / np.sqrt(pi0 * (1 - pi0) / n)
    p_value = 1 - norm.cdf(z)
    return p, z, p_value


def two_prop_equal_test(n1, n2, p1, p2):
    """
    检验两个总体中的比例值是否相等
    研究假设：pi1-pi2≠0
    原假设：pi1-pi2=0
    n1:样本1的规模，如男生200人
    n2:样本1的规模，如女生200人
    p1:样本1的比例，如男生200人中，有27%的赞成某一举措。
    p2:样本2的比例，如女生200人中，有35%的赞成某一举措。
    """
    n1 = n1
    n2 = n2
    p1 = p1
    p2 = p2
    p = (p1 * n1 + p2 * n2) / (n1 + n2)
    z = (p1 - p2) / np.sqrt(p * (1 - p) * (1 / n1 + 1 / n2))
    p_value = norm.cdf(z)
    return (z, p_value)


def gen_mcq_df(df, x, pattern='┋'):
    """
    定义一个针对问卷星数据多选题的分析函数
    df: 数据框
    x: 问卷星中定义的多选题
    返回值： 一个包含所有选项及出现次数和所占比例的数据框
    如：
    |选项|次数|比例（%）|
    |--|--|--|
    |单纯根据个人喜爱|20|5.5|
    |广告内容较感兴趣|20|5.5|
    |喜欢的明星代言|20|5.5|
    |视频广告|20|5.5|
    """
    # 按照指定分隔符将多选题字符串转化包含多个选项的列表
    df['temp'] = df[x].str.split(pattern)
    # 初始化列表，用于保存所有多选题选项
    mcq_items = []
    # 循环所有个案，获取所有多选题选项
    for g in df['temp']:
        for label in g:
            # print(label)
            mcq_items.append(label)
    # 将多选题选项去重后转化为列表，方便构造dataframe
    result = list(set(mcq_items))
    # 构造包含选项、次数和比例的空表
    df_mcq_1 = pd.DataFrame(data=np.zeros([len(result), 2]),
                            index=result,
                            columns=['次数', '比例'])
    # 通过循环获取每个选项在多选题中累次出现的次数
    for i in df[x]:
        for label in result:
            if str(i).__contains__(label):
                df_mcq_1.loc[label, '次数'] += 1
    # 生成比例列
    df_mcq_1['比例'] = df_mcq_1['次数'] / df.shape[0] * 100

    return df_mcq_1.astype({'次数': "int"})


def set_ordered_by_meta(metadata, df, col):
    """ 依据SPSS中的值标签及其顺序，将指定变量设定为有序变量 """
    cat_dtype = CategoricalDtype(
        categories=metadata.variable_value_labels[col].values(), ordered=True)
    return df.astype({col: cat_dtype})


def p_result(p: float) -> str:
    """
    根据接收到的p值，给出判断结果字典。

    参数：
    p： 各种检验得到的p值。

    返回值：
    以字典形式保存的判断结果。
    """
    if p >= 0.05:
        return {
            'p': f'p={round(p,3)}>0.05',
            'tex_p': 'p>0.05',
            'conclusion': '接收虚无假设，拒绝研究假设。',
        }
    elif p >= 0.01:
        return {
            'p': f'p={round(p,3)}<0.05',
            'tex_p': 'p<0.05',
            'conclusion': '拒绝虚无假设，接收研究假设。',
        }
    elif p >= 0.001:
        return {
            'p': f'p={round(p,3)}<0.01',
            'tex_p': 'p<0.01',
            'conclusion': '拒绝虚无假设，接收研究假设。',
        }
    else:
        return {
            'p': f'p={round(p,3)}<0.001',
            'tex_p': 'p<0.001',
            'conclusion': '拒绝虚无假设，接收研究假设。',
        }


def to_sav(df, metadata, filename):
    """
    保存SPSS文件

    要以SPSS格式保存时，Dataframe中变量的值需要以编码的数字形式保存。
    """
    pyreadstat.write_sav(df,
                         filename,
                         variable_value_labels=metadata.variable_value_labels,
                         variable_measure=metadata.variable_measure,
                         variable_format=metadata.original_variable_types)


def get_key(dct, value):
    """ 获取字典中指定值对应的第一个键 """
    result = [k for (k, v) in dct.items() if v == value]
    if result:
        return result[0]
    else:
        return None


def label_to_code(x, col, metadata):
    """ 获取指定值对应的编码值 """
    return get_key(metadata.variable_value_labels[col], x)


def set_label_to_code(df, metadata):
    """ 将SPSS文件中的文字标签转化为数值 """
    for col in df.columns[df.dtypes == 'category']:
        df[col] = df[col].apply(label_to_code, col=col, metadata=metadata)
    return df


def draw_on_eta2(coefficient: int) -> str:
    """
    根据coefficient的值给出建议内容
    按照J.Cohen 提出的标准，0.01时为小效应，0.06时为中等效应，而0.14为大效应。
    Ferguson( 2009) 总结的社会科学领域小0.04、中0.25、大0.64三种参数水平所对应的各类效应量
    指标临界参考值比 Cohen( 1992) 提出的标准更为严格。
    """
    result = ''
    if coefficient >= 0.14:
        result = '高度相关'
    elif coefficient >= 0.06:
        result = '中度相关'
    elif coefficient >= 0.01:
        result = '低度相关'
    else:
        result = '极弱相关或不相关'
    return result


def draw_on_r(coefficient: int) -> str:
    """根据coefficient的值给出建议内容"""
    result = ''
    if coefficient >= 0.56:
        result = '极强相关'
    elif coefficient >= 0.25:
        result = '中等'
    elif coefficient >= 0.06:
        result = '低度相关'
    else:
        result = '极弱相关或不相关'
    return result


def gen_scatter(df, x, y) -> str:
    """
    功能：

    生成散点图

    参数：

    df： DataFrame
    col: DataFrame中的变量，应该为类别变量
    """

    set_plt()

    # 准备数据
    # print(df[x].dtype)
    if df[x].dtype == 'category':
        x1 = df[x].cat.codes
    else:
        x1 = df[x]
    if df[y].dtype == 'category':
        y1 = df[y].cat.codes
    else:
        y1 = df[y]

    # 绘图
    # 创建图
    fig, ax = plt.subplots()
    # 绘制散点图
    # print(x, y)
    art1 = ax.scatter(x1, y1)
    # 设定细节内容
    # 设置x轴变量名称
    ax.set_xlabel(f'{x}')
    # 设置y轴最大值
    ax.set_ylabel(f'{y}')
    plt.show()


def draw_on_corr(coefficient: int) -> str:
    """根据coefficient的值给出建议内容"""
    result = ''
    if coefficient >= 0.8:
        result = '极强相关'
    elif coefficient >= 0.6:
        result = '强相关'
    elif coefficient >= 0.4:
        result = '中等相关'
    elif coefficient >= 0.2:
        result = '弱相关'
    else:
        result = '极弱相关或不相关'
    return result


def goodmanKruska_tau_y(df, x: str, y: str) -> float:
    '''
    古德曼和克鲁斯卡尔 tau(Goodman and Kruskal's tau measure)
    是一种计算两个定类变量相关程度的系数，值介于0-1之间，具有消除误差比例的意义。
    该方法的特点是在计算系数值时，会包含所有的边缘次数和条件次数，故其敏感性高于Lambda系数。
    如果两个变量是不对称关系，最好选用tau-y来简化两个变量的相关情况。

    参考文献：
    李沛良. (2001). 社会研究的统计应用. 社会科学文献出版社

    参数：

    df：DataFrame
    x: 作为自变量的定类变量
    y：作为因变量的定类变量

    返回值：
    tau-y系数，介于0-1之间
    '''
    # 取得条件次数表
    cft = pd.crosstab(df[y], df[x], margins=True)
    # 取得全部个案数目
    n = cft.at['All', 'All']
    # 初始化变量
    E_1 = E_2 = tau_y = 0

    # 计算E_1
    for i in range(cft.shape[0] - 1):
        F_y = cft['All'][i]
        E_1 += ((n - F_y) * F_y) / n
    # 计算E_2
    for j in range(cft.shape[1] - 1):
        for k in range(cft.shape[0] - 1):
            F_x = cft.iloc[cft.shape[0] - 1, j]
            f = cft.iloc[k, j]
            E_2 += ((F_x - f) * f) / F_x
    # 计算tauy
    tau_y = (E_1 - E_2) / E_1

    return tau_y


def ordinal_desc(df, col: str = ''):
    """
    功能：
    对传入的有序类型变量进行描述统计，生成表和柱状图。

    参数：
    df: DataFrame
    col: DataFrame中需要分析的变量，该变量为定类、定序变量

    2022-10-31 增加数量为0的类别的功能

    """
    # 初始化结果DataFrame
    result = pd.DataFrame()
    # 删除数量为0的类别
    df[col] = df[col].cat.remove_unused_categories()
    # 获得变量的内容
    result[f'{col}'] = df[col].value_counts(sort=False).index
    # 获得变量内容出现的次数
    result['个数'] = df[col].value_counts(sort=False).values
    # 获得各选项占比
    result['百分比'] = df[col].value_counts(normalize=True,
                                         sort=False).values * 100
    # 累计百分比
    result['累计百分比（%）'] = result['百分比'].values.cumsum()

    total_row = pd.Series({
        F"{col}": '总和',
        '个数': result['个数'].sum(),
        '百分比': result['百分比'].sum(),
        '累计百分比（%）': None,
    }).to_frame().T
    temp = pd.concat([result, total_row], ignore_index=True)
    temp['百分比'] = temp['百分比'].apply(lambda x: round(x, 2))
    temp['累计百分比（%）'] = temp['累计百分比（%）'].apply(lambda x: round(x, 2))
    # temp.fillna('', inplace=True)
    return temp


def set_plt():
    """ 图形参数设置 """
    # 设置字体
    plt.rcParams["font.sans-serif"] = ["SimHei"]
    # 该语句解决图像中的“-”负号的乱码问题
    plt.rcParams["axes.unicode_minus"] = False
    # 设置图形大小 大小的单位为英寸 inch
    plt.rcParams["figure.figsize"] = (6.4, 4.8)
    # 设置图形分辨率，出版物对图片的分辨率要求从150到300不等。
    plt.rcParams["figure.dpi"] = 200


def show_bar(df, col, sort=True):
    """
    功能：

    为数据框中给定的类别变量生成柱状图。

    参数：

    df： DataFrame

    col: DataFrame中的变量，应该为类别变量

    """

    set_plt()

    # 准备数据

    x = df[f'{col}'].value_counts(sort=sort).index
    y = df[f'{col}'].value_counts(sort=sort, normalize=True).values * 100

    # 绘图
    # 创建图
    fig, ax = plt.subplots()
    # 绘制柱状图
    rects1 = ax.bar(x, y)
    # 设定细节内容
    # 设置x轴变量名称
    ax.set_xlabel(f'{col}')
    # 设置y轴最大值
    ax.set_ylim(ymax=100)
    ax.bar_label(rects1, fmt="%.1f", padding=3)
    plt.show()


def gen_percent_table(df='', col='', sort=True):
    """ 生成类别变量的频数频率表 """
    b = pd.DataFrame()
    b[F'{col}'] = df[F'{col}'].value_counts(sort=sort).index
    b['个数'] = df[F'{col}'].value_counts(sort=sort).values
    b['百分比'] = df[F'{col}'].value_counts(sort=sort,
                                         normalize=True).values * 100
    total_row = pd.Series({
        F"{col}": '总和',
        '个数': b['个数'].sum(),
        '百分比': b['百分比'].sum(),
    }).to_frame().T
    # 联结两个dataframe
    temp = pd.concat([b, total_row], ignore_index=True)
    temp['百分比'] = temp['百分比'].apply(lambda x: round(x, 2))
    return temp


def read_spss(filename):
    """ 使用常用设置读取SPSS文件 """
    df0, metadata = pyreadstat.read_sav(filename, apply_value_formats=True)
    return df0, metadata


def print_all_cats(df):
    """ 打印Dataframe全部类别变量的取值及次数 """
    for col in df.columns[df.dtypes == 'category']:
        print(df[col].value_counts())


def print_all_int(df):
    """ 打印Dataframe全部int变量的取值范围 """
    for col in df.columns[df.dtypes == 'int']:
        print(df[col].describe())


def print_all_float(df):
    """ 打印Dataframe全部float变量的取值范围 """
    for col in df.columns[df.dtypes == 'float']:
        print(df[col].describe())


 